package schedule

import (
	"context"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"migration-tool/adapter"
	"migration-tool/common"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
)

var bannerMap map[string][]string

var aoneBanner []string
var gitlabBanner []string
var giteeBanner []string

func init() {
	bannerMap = make(map[string][]string)
	aoneBanner = append(aoneBanner, " =====================================================================")
	aoneBanner = append(aoneBanner, "|| * *   #######        ########        ####        ##  ########### ||")
	aoneBanner = append(aoneBanner, "||  *   ##     ##      ##      ##      ## ##      ##   ###          ||")
	aoneBanner = append(aoneBanner, "||     ##      ##     ##        ##    ##  ##     ##   ###           ||")
	aoneBanner = append(aoneBanner, "||    ############   ##         ##   ##   ##    ##   ###########    ||")
	aoneBanner = append(aoneBanner, "||   ##         ##   ##        ##   ##    ##   ##   ###             ||")
	aoneBanner = append(aoneBanner, "||  ##           ##   ##      ##   ##     ##  ##   ###           *  ||")
	aoneBanner = append(aoneBanner, "|| ##            ##    #######    ##      #####   ############  * * ||")
	aoneBanner = append(aoneBanner, " =====================================================================")

	gitlabBanner = append(gitlabBanner, " ====================================================================================================")
	gitlabBanner = append(gitlabBanner, "||  *    #########        ##########    ##########      ##            #######            ########   ||")
	gitlabBanner = append(gitlabBanner, "||     ##         ##         ##            ##          ##            ##     ##          ##       ## ||")
	gitlabBanner = append(gitlabBanner, "||   ##                     ##            ##          ##            ##      ##         ##       ##  ||")
	gitlabBanner = append(gitlabBanner, "||  ##      ######         ##            ##          ##            ###########        #########     ||")
	gitlabBanner = append(gitlabBanner, "|| ##           ##        ##            ##          ##            ##         ##      ##       ##    ||")
	gitlabBanner = append(gitlabBanner, "||  ##         ##        ##            ##          ##            ##           ##    ##       ##     ||")
	gitlabBanner = append(gitlabBanner, "||   #########      ##########        ##          ##########    ##            ##   #########    *   ||")
	gitlabBanner = append(gitlabBanner, " ====================================================================================================")

	giteeBanner = append(giteeBanner, " =======================================================================================")
	giteeBanner = append(giteeBanner, "||  *    #########        ##########    ##########      ############     ############  ||")
	giteeBanner = append(giteeBanner, "||     ##         ##         ##            ##          ###              ###            ||")
	giteeBanner = append(giteeBanner, "||   ##                     ##            ##          ###              ###             ||")
	giteeBanner = append(giteeBanner, "||  ##      ######         ##            ##          ############     ############     ||")
	giteeBanner = append(giteeBanner, "|| ##           ##        ##            ##          ###              ###               ||")
	giteeBanner = append(giteeBanner, "||  ##         ##        ##            ##          ###              ###              * ||")
	giteeBanner = append(giteeBanner, "||   #########      ##########        ##          ############     ############    *   ||")
	giteeBanner = append(giteeBanner, " =======================================================================================")

	bannerMap[adapter.AonePlatformName] = aoneBanner
	bannerMap[adapter.GiteePlatformName] = giteeBanner
	bannerMap[adapter.GitLabPlatformName] = gitlabBanner
}

type AdapterState struct {
	State     int
	StartTime *time.Time
}

const (
	AdapterStateReady   int = 1
	AdapterStateRunning int = 2
	AdapterStateFinish  int = 3
	AdapterStateError   int = 4

	ScheduleInterval int = 10

	MaxRepoRunningTime = 2 * 60.0 * 60.0 // 一个仓库的最大运行时间，超过这个时间会认为这个库应该是失败的
	ErrorTimeOut       = "该仓库克隆超时，判定为失败"
	GlobalLogFileName  = "/global.log"

	AllRepos       = "/all-repos.txt"
	AllFailedRepos = "/failed-repos-"
)

type CoordinatorArgs struct {
	ConfigPath    string
	Export        bool
	UseProgress   bool
	IncrementFlag bool
	Check         bool
	Write         bool
}

type Coordinator struct {
	cfg               common.Config
	ctx               context.Context
	cancel            context.CancelFunc
	adapterStates     map[int64]*AdapterState
	adapterMap        map[int64]adapter.Adapter
	repoInfo          map[int64]*adapter.RepoInfo
	runningAdapterCnt int
	log               *logrus.Logger
	repoDone          bool
	done              bool
}

func FlushRepoInfoByConfig(cfg *common.Config, repos adapter.RepoInfoArray) (adapter.RepoInfoPointArray, error) {
	var flushedRepoInfo []*adapter.RepoInfo
	var err error
	for _, repo := range repos {
		repoName := repo.RepoGroupName + "/" + repo.RepoName
		// 根据excludeRepos排除不需要的仓库
		excludeMatch := false
		for _, excludeRegex := range cfg.ExcludeRepos {
			excludeMatch, err = regexp.MatchString(excludeRegex, repoName)
			if err != nil {
				return nil, err
			}
			if excludeMatch {
				break
			}
		}
		if excludeMatch {
			continue
		}

		// 根据repos获取match的仓库
		matched := false
		for _, pattern := range cfg.IncludeRepos {
			matched, err = regexp.MatchString(pattern, repoName)
			if err != nil {
				return nil, err
			}
			if matched {
				// 匹配了，就将该库加入导出库中
				oneRepo := adapter.CopyRepoInfo(&repo)
				flushedRepoInfo = append(flushedRepoInfo, oneRepo)
				break
			}
		}
	}

	return flushedRepoInfo, nil
}

func MakeCoordinator(args CoordinatorArgs) (c *Coordinator, err error) {
	cfg, err := common.ParseConfig(args.ConfigPath)
	if err != nil {
		return nil, err
	}
	// 完成一些BaseConfig中，非yaml的补充属性（由命令行输入的属性）
	cfg.BaseConfig.UseProgress = args.UseProgress
	cfg.BaseConfig.Export = args.Export
	cfg.BaseConfig.IncrementFlag = args.IncrementFlag
	cfg.BaseConfig.Check = args.Check
	cfg.BaseConfig.Write = args.Write

	// 创建WorkerPool
	adapter.GlobalWorkerPool = adapter.NewWorkerPool(&cfg.BaseConfig)
	// 获取全局日志
	logFilePath := cfg.BaseConfig.ExportDataDir + GlobalLogFileName
	log, err := common.NewGlobalLog(logFilePath)
	if err != nil {
		return nil, err
	}

	cfg.GlobalLog = log
	log.Info("正在获取仓库信息，预计时间1分钟，请耐心等待。。。")
	// 获取所有仓库
	allRepos, err := adapter.GetAllRepoInfo(&cfg)
	if err != nil {
		return nil, err
	}
	log.Info("正在根据配置文件信息筛选仓库。。。")
	// 根据配置文件，去除不符合条件的仓库
	flushedRepos, err := FlushRepoInfoByConfig(&cfg, allRepos)
	if err != nil {
		return nil, err
	}
	log.Info("本次执行，筛选的总仓库数量为:", len(flushedRepos))
	adapter.AssignDefaultOrder(&cfg)

	// 创建WorkerPool
	adapter.GlobalWorkerPool = adapter.NewWorkerPool(&cfg.BaseConfig)

	ctx, cancel := context.WithCancel(context.Background())
	c = &Coordinator{
		cfg:               cfg,
		ctx:               ctx,
		cancel:            cancel,
		adapterStates:     make(map[int64]*AdapterState),
		adapterMap:        make(map[int64]adapter.Adapter),
		repoInfo:          make(map[int64]*adapter.RepoInfo),
		runningAdapterCnt: 0,
		log:               log,
		repoDone:          false,
		done:              false,
	}

	for _, val := range flushedRepos {
		c.adapterStates[val.RepoId] = &AdapterState{
			State:     AdapterStateReady,
			StartTime: nil,
		}
		c.repoInfo[val.RepoId] = val
	}

	// 输出banner
	platformName := adapter.GetPlatformName(cfg.BaseConfig.ExportPlatformName)
	for _, val := range bannerMap[platformName] {
		log.Info(val)
	}

	// 将所有已授权仓库存入文件中
	var repoNameString []byte
	sort.Sort(allRepos)
	for _, v := range allRepos {
		repoNameString = append(repoNameString, []byte(fmt.Sprintf("- \"^%s/%s$\"\n", v.RepoGroupName, v.RepoName))...)
	}
	repoNameFilePath := c.cfg.BaseConfig.ExportDataDir + AllRepos
	err = os.WriteFile(repoNameFilePath, repoNameString, os.ModePerm)
	if err != nil {
		return nil, err
	}
	c.log.Info(fmt.Sprintf("请查看文件\"%s\"，其中包含所有可操作仓库", repoNameFilePath))

	return c, nil
}

func (c *Coordinator) Merge() error {
	err := adapter.MergeExportedData(&c.cfg)
	if err != nil {
		return err
	}
	return nil
}

func (c *Coordinator) ShowProgress() (err error) {
	if c.cfg.BaseConfig.Export {
		c.log.Info("====================以下为仓库[导出]任务的完成情况====================")
	} else {
		c.log.Info("====================以下为仓库[导入]任务的完成情况====================")
	}
	for _, val := range c.repoInfo {
		var progress string
		progress, err = adapter.ReadRepoProgress(&c.cfg.BaseConfig, val)
		if err != nil {
			return err
		} else {
			c.log.Info(progress)
		}
	}

	return err
}

func (c *Coordinator) DryRun() error {
	// 执行DryRun

	if c.cfg.BaseConfig.Export {
		c.log.Info("====================以下为本次[导出]相关仓库信息====================")
	} else {
		c.log.Info("====================以下为本次[导入]相关仓库信息====================")
	}
	for _, val := range c.repoInfo {
		msg := fmt.Sprintf("仓库名:[%s/%s],仓库Id:[%s]", val.RepoGroupName, val.RepoName, strconv.FormatInt(val.RepoId, 10))
		c.log.Info(msg)
	}

	return nil
}

func (c *Coordinator) Start() error {
	// 这里需要执行一些开始调度前的准备任务
	if c.cfg.BaseConfig.Export {
		// 这里必须删除merge的记录，代表将开始调度
		err := adapter.UpdateMergeProgress(&c.cfg)
		if err != nil {
			return err
		}
	} else {
		finishMerge, err := adapter.CheckMergeFinished(&c.cfg)
		if err != nil {
			return err
		}
		if !finishMerge {
			c.log.Error("请先执行Merge操作后，再执行导入")
			return errors.New("请先执行Merge操作后，再执行导入")
		}
	}

	// 执行不分库的导入导出任务
	err := adapter.StartGlobalTask(&c.cfg)
	if err != nil {
		return err
	}

	go c.schedule()
	go c.readInput()

	return nil
}

func (c *Coordinator) Done() bool {
	return c.done
}

func (c *Coordinator) readInput() {
	for {
		var input string
		_, err := fmt.Scan(&input)
		if err != nil {
			panic(err)
		}
		input = strings.ReplaceAll(input, "\n", "")
		if input == "stop" {
			c.cancel()
			break
		}
	}

	return
}

func (c *Coordinator) schedule() {
	stop := false
	for !c.repoDone {
		select {
		case <-c.ctx.Done():
			// 接收到Stop命令，准备退出
			stop = true
			for k, v := range c.adapterMap {
				_ = v.Stop()
				recordGlobalLog(c.log, c.repoInfo[k].RepoGroupName, c.repoInfo[k].RepoName, "仓库正在停止", nil)
				c.adapterStates[k].State = AdapterStateError
			}
			break
		default:
			c.scanAdapterState()
			time.Sleep(time.Duration(ScheduleInterval) * time.Second)
		}
		if stop {
			break
		}
	}

	if stop {
		// 如果是接受stop命令，则安全退出，并记录失败库
		c.scanAdapterState()
	}
	c.done = true
	//adapter.GlobalWorkerPool.Stop()
}

func (c *Coordinator) scanAdapterState() {
	allRepoDone := true
	for k, v := range c.adapterStates {
		switch v.State {
		case AdapterStateReady:
			allRepoDone = false
			if c.runningAdapterCnt < c.cfg.BaseConfig.MaxParallelRepoNum {
				args := adapter.DefaultAdapterArgs{
					RepoInfo: c.repoInfo[k],
				}
				oneAdptr, err := adapter.CreateOneAdapter(&c.cfg, args)
				if err != nil {
					recordGlobalLog(c.log, c.repoInfo[k].RepoGroupName, c.repoInfo[k].RepoName, "", err)
					os.Exit(1)
				}
				c.adapterMap[k] = oneAdptr
				err = oneAdptr.Start()
				if err != nil {
					recordGlobalLog(c.log, c.repoInfo[k].RepoGroupName, c.repoInfo[k].RepoName, "", err)
					oneAdptr.Stop()
					delete(c.adapterMap, k)
					continue
				}
				c.adapterStates[k].State = AdapterStateRunning
				startTime := time.Now()
				c.adapterStates[k].StartTime = &startTime
				c.runningAdapterCnt += 1

				// 不记录日志了
				//recordGlobalLog(c.log, c.repoInfo[k].RepoGroupName, c.repoInfo[k].RepoName, "创建成功，开始进行调度", nil)
			}
		case AdapterStateRunning:
			allRepoDone = false
			adptr := c.adapterMap[k]
			done := adptr.Done()
			if done {
				c.runningAdapterCnt -= 1
				adptrMsg, err := adptr.GetMessage()
				if err != nil {
					// 这里的错误一定是系统错误，汇报后不再重试
					recordGlobalLog(c.log, c.repoInfo[k].RepoGroupName, c.repoInfo[k].RepoName, "", err)
					c.adapterStates[k].State = AdapterStateError
				} else {
					recordGlobalLog(c.log, c.repoInfo[k].RepoGroupName, c.repoInfo[k].RepoName, adptrMsg, nil)
					c.adapterStates[k].State = AdapterStateFinish
				}
				// 释放资源
				delete(c.adapterMap, k)
			} else {
				if time.Now().Sub(*c.adapterStates[k].StartTime) >= MaxRepoRunningTime*time.Second {
					adptr.Stop()
					c.runningAdapterCnt -= 1
					recordGlobalLog(c.log, c.repoInfo[k].RepoGroupName, c.repoInfo[k].RepoName, "", errors.New(ErrorTimeOut))
					c.adapterStates[k].State = AdapterStateError
					c.adapterStates[k].StartTime = nil
					delete(c.adapterMap, k)
				}
			}
		case AdapterStateFinish:
		case AdapterStateError:
		default:
			panic("schedule scanTaskState encounter error adapterState")
		}
	}

	if allRepoDone {
		c.log.Info("全部仓库已完成！仓库扫描停止！")
		var errorRepoName []string
		for repoId, val := range c.adapterStates {
			if val.State == AdapterStateError {
				failedRepoStr := fmt.Sprintf("- \"^%s/%s$\"\n", c.repoInfo[repoId].RepoGroupName, c.repoInfo[repoId].RepoName)
				errorRepoName = append(errorRepoName, failedRepoStr)
			}
		}
		if len(errorRepoName) > 0 {
			failedPath := c.cfg.BaseConfig.ExportDataDir + "/" + time.Now().Format(time.DateOnly)
			// 确保文件夹存在
			if err := os.MkdirAll(failedPath, 0755); err != nil {
				// 处理错误
				panic(err)
			}
			// 将所有处理失败的仓库名写入文件
			repoNameFilePath := failedPath + AllFailedRepos + strings.ReplaceAll(time.Now().Format(time.TimeOnly), ":", "-") + ".txt"
			sort.Strings(errorRepoName)
			failedRepoStr := strings.Join(errorRepoName, "")
			_ = os.WriteFile(repoNameFilePath, []byte(failedRepoStr), os.ModePerm)
			c.log.Info(fmt.Sprintf("共%d个仓库失败，请查看文件\"%s\"，其中包含执行失败的仓库名", len(errorRepoName), repoNameFilePath))
		} else {
			c.log.Info(fmt.Sprintf("共%d个仓库全部执行成功，", len(c.repoInfo)))
		}
		c.repoDone = true
	} else {
		running := 0
		ready := 0
		finish := 0
		//runningRepoName := ""
		var runningRepoName []string
		for repoId, val := range c.adapterStates {
			if val.State == AdapterStateReady {
				ready++
			} else if val.State == AdapterStateRunning {
				running++
				runningRepoName = append(runningRepoName, c.repoInfo[repoId].RepoGroupName+"/"+c.repoInfo[repoId].RepoName)
			} else {
				finish++
			}
		}
		var logMsg string
		logMsg = strconv.Itoa(running) + "个仓库正在运行; 剩余" + strconv.Itoa(ready) + "个仓库未开始; 已完成" + strconv.Itoa(finish) + "个仓库..."
		c.log.Info(logMsg)
		if len(runningRepoName) > 0 {
			sort.Strings(runningRepoName)
			printRepoEdge := 10
			var repoMsg string
			if len(runningRepoName) > printRepoEdge {
				repoMsg = "正在运行的仓库名:【" + strings.Join(runningRepoName[:printRepoEdge], "、") + "...】"
			} else {
				repoMsg = "正在运行的仓库名:【" + strings.Join(runningRepoName, "、") + "】"
			}
			c.log.Info(repoMsg)
		}

	}
}

func recordGlobalLog(log *logrus.Logger, repoGroupName string, repoName string, msg string, err error) {
	if err == nil {
		infoMsg := "仓库-【" + repoGroupName + "/" + repoName + "】" + msg
		log.Info(infoMsg)
	} else {
		errMsg := "仓库-【" + repoGroupName + "/" + repoName + "】执行过程中出现错误:" + err.Error()
		log.Error(errMsg)
	}
}

func (c *Coordinator) RecordLog(msg string, err error) {
	if err != nil {
		c.log.Error(err.Error())
	} else {
		c.log.Info(msg)
	}
}

func (c *Coordinator) RemoveIncrement() error {
	c.log.Info("正在进行增量导出文件的删除工作，请等待。。。")
	return adapter.RemoveIncrement(&c.cfg)
}
